package com.star.security.filter;

import com.star.security.annotations.Authorize;
import com.star.security.authentication.AnonymousAuthenticationToken;
import com.star.security.authentication.Authentication;
import com.star.security.context.TokenContextHolder;
import com.star.security.exception.AuthenticationException;
import com.star.security.exception.ForbiddenException;
import com.star.security.exception.UnauthorizedException;
import com.star.security.user.UserDetails;
import org.springframework.web.method.HandlerMethod;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

/**
 * 访问检查
 * 默认所以访问都要权限，除非anonymousUris中配置（匿名已认证）
 */
public class AccessCheckerImpl implements AccessChecker {

    /**
     * 权限标识符号
     */
    private static final String AUTHORITIES_NOTATION = ":";

    @Override
    public boolean check(HandlerMethod handlerMethod) throws AuthenticationException {
        Authentication authentication = TokenContextHolder.getContext();
        if (authentication.isAuthenticated()) {
            return true;
        }
        // 匿名用户且未认证
        if (AnonymousAuthenticationToken.isUnauthenticated(authentication)) {
            throw new UnauthorizedException("未认证");
        }
        // 超管也不用验证
        UserDetails user = (UserDetails) authentication.getPrincipal();
        if (user.isSuperAdmin()) {
            return true;
        }
        // 获取方法上的注解
        Authorize annotation = handlerMethod.getMethod().getAnnotation(Authorize.class);
        boolean checked = this.authorizeChecker(user, annotation);
        if (!checked) {
            throw new ForbiddenException("拒绝访问");
        }
        return true;
    }

    /**
     * 检查授权
     * -带:为权限标识，不带:为角色
     *
     * @param user       使用者
     * @param annotation 注释
     */
    private boolean authorizeChecker(UserDetails user, Authorize annotation) {
        if (annotation == null) {
            return true;
        }
        String[] values = annotation.value();
        Set<String> roles = Optional.ofNullable(user.getRoles()).orElse(new HashSet<>());
        Set<String> authorities = Optional.ofNullable(user.getAuthorities()).orElse(new HashSet<>());
        // 多个之间是 或关系
        boolean pass = false;
        for (String value : values) {
            boolean isAuthorities = value.contains(AUTHORITIES_NOTATION);
            if ((!isAuthorities && roles.contains(value)) || (isAuthorities && authorities.contains(value))) {
                pass = true;
                break;
            }
        }
        return pass;
    }
}
